[cpp]StringView学习
string_view
是cpp17
之后提供了一个模板类. 它维护一个对于底层字符数组的只读视图, 可以在多种场景下提高程序的性能.
StringView的优势
传递字符串参数的时候, 在不进行修改操作的时候通常都会使用const string&
来接收实参, 其在接收字符串字面值、字符数组和字符串指针的时候还是会存在构造字符串的问题. 即先生成一个匿名string
对象, 然后const string&
绑定到该匿名对象上去. 当字符串很大时, 会存在严重的性能问题.
substr
函数. string
的substr
函数会返回一个string
对象. 该操作造成的拷贝会影响程序的性能(在只读要求下).
针对以上问题: string_view
能够较好的解决. 因为其是一个只读视图, 用string_view
作为参数拷贝的开销是很低的, 以及string_view
的substr
函数返回的还是一个string_view
. 避免了重新生成一个新的字符串的大开销操作.
string_view
在cpp17
以前的一些第三方库中有自己的实现:
leveldb
的Slice实现.google
基础库Abseil
的StringView实现.
StringView的实现
字面值
可以使用string_view sv = "just test"sv;
字面值来生成string_view
.
1 | constexpr string_view operator"" sv(const char* _Str, size_t _Len) noexcept { |
数据成员
string_view
的模板类中, 只包含了两个私有数据成员_Mydata
和_Mysize
. _Mydata
为指向底层字符数组的指针, 而_Mysize
表示string_view
的可见长度.
1 | // MSVC-14.30.30705: xstring |
构造函数
1 | // 默认构造函数 |
成员函数
string_view
的成员函数几乎和string
没有差异. 不过需要注意的是: string_view
的成员函数无法修改底层的数据, 比如operator[]
返回constexpr
引用. 其能修改的只有_Mydata
指针的指向以及_Mysize
的大小(即string_view
的可见范围).
1 | constexpr const_reference operator[]( size_type pos ) const; |
修改_Mydata
指针和_Mysize
大小的函数:
1 | // Moves the start of the view forward by n characters. The behavior is undefined if n > size(). |
String和StringView的互相构造
- string_view构造string
可以使用string_view
直接调用string
的构造函数来初始化一个string
对象. 并且string
不与string_view
共享底层数据. 注意只能显式调用string
来讲string_view
进行转换, 因为其实对底层数据的拷贝.
1 | template< class StringViewLike > |
测试代码
1 | // 通过字面量创建string_view, 先调用constexpr string_view operator"" sv返回string_view对象, 然后赋值拷贝 |
- string构造string_view
string_view
可以使用string
来构造, 原因是string
有string_view
的类型转换函数. 其可以进行隐式的转换, 因为string
转换成string_view
只是生成了一个只读视图.
1 | // string -> string_view 的类型转换函数 |
测试代码
1 | string str = "abc"; |
StringView的使用注意事项
- 资源所有权的问题:
It is the programmer’s responsibility to ensure that std::string_view does not outlive the pointed-to character array
string_view
的生命周期和其观察的底层字符串的生命周期是无关的. 因此如果底层字符串先于string_view
析构, 那么当再次访问时, 其行为是未定义的.
底层字符串的修改问题:
1
2
3
4
5
6
7
8
9
10string str = "abc";
string_view sv(str);
string s = move(str);
cout << "sv = " << sv << ", str = " << str << ", s = " << s << endl;
/*
sv = bc, str = , s = abc
*/因此我们需要保证使用
string_view
观察的底层字符串其必须不能被修改, 否则会造成不可预期的后果.
终结符的问题:
我们都知道
c
和cpp
的字符串是以\0
作为终结符的. 而string_view
是限定了可见长度, 当我们使用string_view
时, 需要注意其不能使用strlen
系列的函数进行字符串的操作, 因为字符串终结符的可见性可能被remove_suffix
移除掉了.1
2
3
4
5
6
7
8
9
10const string str = "abcdefg";
string_view sv(str);
sv.remove_prefix(1);
sv.remove_suffix(2);
cout << "sv = " << sv << ", strlen(sv) = " << strlen(sv.data()) << endl;
/* Error
sv = bcde, strlen(sv) = 6
*/
总结
string_view
解决了只读字符串某些场景下的性能问题. 但其在使用的时候还需要注意其引入的问题, 其不像lock_guard
、unique_lock
、shared_ptr
、weak_ptr
或unique_ptr
等资源管理类一样和被管理资源的生命周期有着较为紧密的联系, 因此在使用的时候需要注意上述的几个问题.
参考
cppreference-string_view
[现代C++]性能控的工具箱之string_view
C++17剖析:string_view的实现,以及性能
[cpp]StringView学习